/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package automenta.vivisect.swing;

import automenta.vivisect.Vis;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import processing.core.PApplet;
import static processing.core.PConstants.DOWN;
import static processing.core.PConstants.LEFT;
import static processing.core.PConstants.RIGHT;
import static processing.core.PConstants.UP;
import processing.event.MouseEvent;

/**
 *
 * @author me
 */
public class PCanvas extends PApplet implements HierarchyListener {

    int mouseScroll = 0;

    Hnav hnav = new Hnav();

    float zoom = 0.1f;
    float scale = 1f;
    float selection_distance = 10;
    float FrameRate = 25f;

    boolean drawn = false;
    float motionBlur = 0.0f;
    private final Vis vis;

    float camspeed = 20.0f;
    float scrollcammult = 0.92f;
    boolean keyToo = true;
    float savepx = 0;
    float savepy = 0;
    int selID = 0;

    float difx = 0;
    float dify = 0;
    int lastscr = 0;
    boolean EnableZooming = true;
    float scrollcamspeed = 1.1f;

    public PCanvas() {
        this(null);
    }
    
    public PCanvas(Vis vis) {
        this(vis, 26);
    }

    public PCanvas(Vis vis, int frameRate) {
        super();
        this.FrameRate = frameRate;
        init();
        if ((vis == null) && (this instanceof Vis)) {
            //for subclasses:
            vis = (Vis)this;
        }
        this.vis = vis;
        vis.init(this);
    }

    float MouseToWorldCoordX(final int x) {
        return 1 / zoom * (x - difx - width / 2);
    }

    float MouseToWorldCoordY(final int y) {
        return 1 / zoom * (y - dify - height / 2);
    }

    public float getPanX() {
        return difx;
    }
    public float getPanY() {
        return dify;
    }
    
    public void setPanX(float px) { difx = -px; }
    public void setPanY(float py) { dify = -py; }
    
    public float getCursorX() {
        return MouseToWorldCoordX(mouseX);
    }

    public float getCursorY() {
        return MouseToWorldCoordY(mouseY);
    }

    @Override
    protected void resizeRenderer(int newWidth, int newHeight) {
        if ((newWidth > 0) && (newHeight > 0)) {
            super.resizeRenderer(newWidth, newHeight);
            drawn = false;
        }
    }

    public void mouseScrolled() {
        hnav.mouseScrolled();
    }

    @Override
    public void keyPressed() {
        hnav.keyPressed();
    }

    @Override
    public void mouseMoved() {
    }

    @Override
    public void mouseReleased() {
        hnav.mouseReleased();
        super.mouseReleased();
    }

    @Override
    public void mouseDragged() {
        hnav.mouseDragged();
        super.mouseDragged();
    }

    @Override
    public void mousePressed() {
        hnav.mousePressed();
        super.mousePressed();
    }

    @Override
    public void draw() {
        

        if (drawn) {
            return;
        }

        drawn = false;

        if (motionBlur > 0) {
            fill(0, 0, 0, 255f * (1.0f - motionBlur));
            rect(0, 0, getWidth(), getHeight());
        } else {
            background(0, 0, 0);//, 0.001f);
        }

        hnav.applyTransform();
        vis.draw(g);
    }

    public void setMotionBlur(float motionBlur) {
        this.motionBlur = motionBlur;
    }

    public float getMotionBlur() {
        return motionBlur;
    }

    
    
    @Override
    public void mouseWheel(MouseEvent event) {
        super.mouseWheel(event);
        mouseScroll = -event.getCount();
        mouseScrolled();
    }

    @Override
    public void setup() {

        //size(500,500,P3D);
        frameRate(FrameRate);

        if (isGL()) {
            textFont(createDefaultFont(16));
            smooth();
            System.out.println("Processing.org enabled OpenGL");
        }
        
    }
    
   
    @Override
    public void addNotify() {
        super.addNotify();
        addHierarchyListener(this);
    }

    @Override
    public void removeNotify() {
        removeHierarchyListener(this);
        super.removeNotify();
    }

    @Override
    public void hierarchyChanged(HierarchyEvent e) {
        if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
            boolean showing = isShowing();
            onShowing(showing);
                        
        }
    }    

    
    protected void onShowing(boolean showing) {
        vis.onVisible(showing);
        
        if (showing) {
            //restart loop? can this even happen
            //throw new RuntimeException("if this happens, looping state should be restored here");
        }
        else {
            noLoop();
        }
    }
    

    public PCanvas setFrameRate(float frameRate) {
        this.FrameRate = frameRate;
        frameRate(FrameRate);
        return this;
    }

    public float getFrameRate() {
        return frameRate;
    }
    
    public PCanvas setZoom(float x, float y, float z) {
        setPanX(x);
        setPanY(y);
        setZoom(z);
        return this;
    }

    public PCanvas setZoom(float f) {
        zoom = f;
        return this;
    }

    public float getZoom() {
        return zoom;
    }

    /** zoom to a rectangular region */
    public void setZoom(float cx, float cy, float width, float height) {
        //TODO add margin, right-click zoom out
        
        //System.out.println("auto-zoom: " + cx + " " + cy + " " + width + " " + height);
        
        //https://github.com/automenta/automenta.spacegraphj/blob/master/spacegraph/automenta/spacegraph/control/FractalControl.java#L128
        
       // float targetZ = getTargetDepth(width, height); //TODO calculate correct height
        float targetZ = this.width / Math.max(width,height);
        
        //System.out.print(getPanX() + " " + getPanY() + " " + getZoom() + " --> ");        
        float tx = (cx) * targetZ;
        float ty = (cy) * targetZ;
        //System.out.println(tx + " " + ty + " " + targetZ);
        setZoom(tx, ty, targetZ);

    }
    /*static float getTargetDepth(float width, float height) {
        float zoomDilation = 1.0f;
        float r = Math.max(width, height) / 2.0f * zoomDilation;
        final float focus = (float)Math.PI/4f;
        return (float) (r * Math.sin(Math.PI / 2.0 - focus / 2.0) / Math.sin(focus / 2.0));
    }*/

    class Hnav {

        private boolean md = false;

        void mousePressed() {
            md = true;
            if (mouseButton == RIGHT) {
                savepx = mouseX;
                savepy = mouseY;
                redraw();
            }
        }

        void mouseReleased() {
            md = false;
        }

        void mouseDragged() {
            if (mouseButton == RIGHT) {
                difx += (mouseX - savepx);
                dify += (mouseY - savepy);
                savepx = mouseX;
                savepy = mouseY;
                redraw();                
            }
        }

        void keyPressed() {
            if ((keyToo && key == 'w') || keyCode == UP) {
                dify += (camspeed);
            }
            if ((keyToo && key == 's') || keyCode == DOWN) {
                dify += (-camspeed);
            }
            if ((keyToo && key == 'a') || keyCode == LEFT) {
                difx += (camspeed);
            }
            if ((keyToo && key == 'd') || keyCode == RIGHT) {
                difx += (-camspeed);
            }
            if (!EnableZooming) {
                return;
            }
            if (key == '-' || key == '#') {
                float zoomBefore = zoom;
                zoom *= scrollcammult;
                difx = (difx) * (zoom / zoomBefore);
                dify = (dify) * (zoom / zoomBefore);
            }
            if (key == '+') {
                float zoomBefore = zoom;
                zoom /= scrollcammult;
                difx = (difx) * (zoom / zoomBefore);
                dify = (dify) * (zoom / zoomBefore);
            }
            redraw();            
            drawn = false;
        }

        void Init() {
            difx = -width / 2;
            dify = -height / 2;
        }

        void mouseScrolled() {
            if (!EnableZooming) {
                return;
            }
            float zoomBefore = zoom;
            if (mouseScroll > 0) {
                zoom *= scrollcamspeed;
            } else {
                zoom /= scrollcamspeed;
            }
            difx = (difx) * (zoom / zoomBefore);
            dify = (dify) * (zoom / zoomBefore);
            redraw();
            drawn = false;
        }

        void applyTransform() {
            translate(difx + 0.5f * width, dify + 0.5f * height);
            scale(zoom, zoom);
        }
    }

//    ////Object management - dragging etc.
//    class Hsim {
//
//        ArrayList obj = new ArrayList();
//
//        void Init() {
//            smooth();
//        }
//
//        void mousePressed() {
//            if (mouseButton == LEFT) {
//                checkSelect();
//            }
//        }
//        boolean dragged = false;
//
//        void mouseDragged() {
//            if (mouseButton == LEFT) {
//                dragged = true;
//                dragElems();
//            }
//        }
//
//        void mouseReleased() {
//            dragged = false;
//            //selected = null;
//        }
//
//        void dragElems() {
//            /*
//             if (dragged && selected != null) {
//             selected.x = hnav.MouseToWorldCoordX(mouseX);
//             selected.y = hnav.MouseToWorldCoordY(mouseY);
//             hsim_ElemDragged(selected);
//             }
//             */
//        }
//
//        void checkSelect() {
//            /*
//             double selection_distanceSq = selection_distance*selection_distance;
//             if (selected == null) {
//             for (int i = 0; i < obj.size(); i++) {
//             Vertex oi = (Vertex) obj.get(i);
//             float dx = oi.x - hnav.MouseToWorldCoordX(mouseX);
//             float dy = oi.y - hnav.MouseToWorldCoordY(mouseY);
//             float distanceSq = (dx * dx + dy * dy);
//             if (distanceSq < (selection_distanceSq)) {
//             selected = oi;
//             hsim_ElemClicked(oi);
//             return;
//             }
//             }
//             }
//             */
//        }
//    }

}
